// SSLLib.h

#pragma once
#include <schannel.h>
#include <sspi.h>

#pragma comment(lib, "Crypt32")
#pragma comment(lib, "Secur32")

using namespace System;

namespace SSLLib
{
	[Serializable]
//	[ClassInterface(ClassInterfaceType::AutoDual)]
	public ref class SSLException : public ApplicationException
	{
	public:
		SSLException() {}
		SSLException(std::string& s): ApplicationException(gcnew String(s.data())) {}
		//SSLException(String* message): ApplicationException(message) {}
  //      SSLException(String* message, Exception* innerException):
		//	ApplicationException(message,innerException) {}
  //      SSLException(String* message, UInt32 error):
		//	ApplicationException(String::Concat(message, Convert::ToString(error))) {}
	};

	public ref class SSLTransport
	{
	public:
		const static int MAX_BUFFER_SIZE = 0x10000;

		SSLTransport();
		~SSLTransport() { this->!SSLTransport(); }
		!SSLTransport();

		delegate void SendCallbackDelegate(array<Byte>^ data);
		delegate array<Byte>^ FetchCallbackDelegate(int maxlen);
		void Handshake(
			String^ hostname, 
			SendCallbackDelegate^ onSendCallback, 
			FetchCallbackDelegate^ onFetchCallback,
			[System::Runtime::InteropServices::OutAttribute] array<Byte>^% extradata);

		array<Byte>^ SSLTransport::Encrypt(array<Byte>^ plain_data);
		int Decrypt(
			array<Byte>^ encrypted_data,  
			SendCallbackDelegate^ onSendCallback, 
			FetchCallbackDelegate^ onFetchCallback,
			SendCallbackDelegate^ onDecryptedDataCallback);

		void Done();
	private:
		HANDLE heap;

		SecurityFunctionTable* pSecurityFunc;
		CredHandle* phClientCredentials;
		CtxtHandle* phContext;
		SecPkgContext_StreamSizes* sizes;

		void CallSend(SendCallbackDelegate^ callbackSend, void* buf, int len)
		{
			if (callbackSend != nullptr && buf != NULL)
			{
				array<Byte>^ data = gcnew array<Byte>(len);
				System::Runtime::InteropServices::Marshal::Copy(IntPtr(buf), data, 0, len);
				callbackSend(data);
			}
		}

		int CallFetch(FetchCallbackDelegate^ callbackFetch, void* buf, int maxlen)
		{
			if (callbackFetch != nullptr && buf != NULL && maxlen != 0)
			{
				array<Byte>^ managed_data = callbackFetch(maxlen);
				pin_ptr<Byte> pinned_managed_data = &managed_data[0];
				if (maxlen < managed_data->Length)
					throw gcnew SSLException(std::string("Provided buffer is not big enough for data read from the network"));
				memcpy(buf, pinned_managed_data, managed_data->Length);
				return managed_data->Length;
			}
			else
				return -1;
		}
	};
}
